import java.io.*;
import java.net.*;
import java.lang.*;

public class Agent implements Runnable, Serializable
{
	//(name,id) rep Agent
	private String name;
	public int id=0;

	//define the type of agent
	public static final int TYPE_UNKNOWN = 0;
	public static final int TYPE_POLICE = 1;
	public static final int TYPE_THIEF =  2;

	//Behaviour the different type of Agent
	private Behaviour behaviour = null;

	Agent(String name) { this.name = name; }

	Agent(String name, int id) {
		this.name = name;	
		this.id = id;
	}

	public int getID() { return id; }
	public String getName() { return name; }

	//set behaviour
	public void setBehaviour(Behaviour behaviour) {
		this.behaviour = behaviour;
		this.behaviour.setAgent(this);
	}



	public void run()
	{
		System.out.println("I am "+name);
		System.out.println("ID: "+id);

		if (behaviour != null) {
			behaviour.action();
		}
	}

	//serialize the Agent
	public void writeObject(ObjectOutputStream out) throws IOException {
		out.writeUTF(name);
		out.writeInt(id);
	}

	public void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException	{
		name = in.readUTF();
		id = in.readInt();
	}

	//give an object,serialize to network
	//
	// format
	//  1. Agent Serialization
	//  2. Behaviour code
	//     2.1 class name
	//     2.2 code length
	//     2.3 code
	//
	// TODO
	//  You need to put the type before this function
	//  MigrationManager.AGENT_LEAVE
	public void serializeToNetwork(Socket sock) {
		ObjectOutputStream out = null;
		try{
			out = new ObjectOutputStream(sock.getOutputStream());
			out.writeObject(this);
			if (behaviour!=null) {
				behaviour.putClassToNetwork(sock);
			}

		}catch(IOException ex) {
			System.out.println(ex);
		}finally {
			try{
			  if (out != null)
				  out.close();
			}catch(IOException ex) { }
		}
	}

	public static void serializeToNetwork(Socket sock, Agent agent) {
		agent.serializeToNetwork(sock);
	}

	// restore from network
	public static Agent restoreFromNetwork(Socket sock) {
		Agent agent = null;
		ObjectInputStream in = null;

		try{
			in = new ObjectInputStream(sock.getInputStream());
			agent = (Agent)in.readObject();
		}catch(IOException ex){
			System.out.println(ex);
		}catch(ClassNotFoundException ex){
			System.out.println(ex);
		}finally{
			try{
				if (in != null) 
					in.close();
			}catch(IOException ex){ }
		}

		return agent;
	}

	//get the type of agent
	public int getType() {
		if (behaviour==null)
			return Agent.TYPE_UNKNOWN;
		else 
			return behaviour.getType();
	}


	public void doLeave() {

		/* connect to loacl migration manager to migrate the agent to destination*/
		// Format:
		//  1. type --> MigrationManager.AGENT_LEAVE
		//  2. ID --> Agent.id
		Socket connect = null;
		DataOutputStream dout = null;

		try{
			connect = new Socket("localhost",MigrationManager.MIGRATION_PORT);
			dout = new DataOutputStream(connect.getOutputStream());
			dout.writeInt(MigrationManager.AGENT_LEAVE);
			dout.writeInt(id);
		}catch(IOException ex) {
			System.out.println(ex);
		}finally {
			try{
				if (connect!=null)
					connect.close();
			}catch(IOException ex) { }
		}
	}

	//simple test of serialization 
	public void test() 
	{
		ByteArrayOutputStream bout = null;
		ObjectOutputStream out=null;

		try{
			bout = new ByteArrayOutputStream(); 
			out = new ObjectOutputStream(bout);
			out.writeObject(this);
		}catch (IOException ex) {
			System.err.println(ex);
		}


		ObjectInputStream in =null;
		Agent ag=null;

		try{
			byte[] bytes = bout.toByteArray();
			ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
			in = new ObjectInputStream(bin);
			ag = (Agent)in.readObject();
		}catch(IOException ex){
			System.err.println(ex);
		}catch(ClassNotFoundException ex){
			System.err.println(ex);
		}finally {
			try{
				out.close();
				in.close();
			}catch(IOException ex){ }
		}

		Thread T = new Thread(ag);
		T.start();
	}

	public static void main(String[] args)
	{
		Agent agent = new Agent("JUN",1234);
	    PoliceBehaviour behaviour = new PoliceBehaviour(agent);
		agent.setBehaviour(behaviour);
		agent.test();
	}

	//
	public int compareTo(Agent other) {
		return this.id - other.id;
	}

}
